﻿using System.IO;
using System;
using System.Threading;
using Fx.Serialization;

namespace Fx.Caching
{
    public abstract class Cache : ICache
    {
        protected const string DefaultCacheDirectory = @"caches\";
        protected const string DefaultCacheFileExtension = ".cache";

        private readonly string _cachePath;
        private readonly JsonSerializer _serializer;

        protected Cache(string cachePath = DefaultCacheDirectory)
        {
            if (string.IsNullOrWhiteSpace(cachePath) || string.IsNullOrEmpty(cachePath))
                cachePath = Path.Combine(Environment.CurrentDirectory, DefaultCacheDirectory);
            _serializer = new JsonSerializer();
            _cachePath = cachePath;
        }

        #region Implements ICacheService interface

        public T Get<T>(string cacheId, Func<T> getItemCallback, int cacheDurationMinutes) where T : class
        {
            return Get(cacheId, getItemCallback, cacheDurationMinutes, false);
        }

        public T Get<T>(string cacheId, Func<T> getItemCallback, int cacheDurationMinutes, bool withSerialization) where T : class
        {
            if (string.IsNullOrEmpty(cacheId) || string.IsNullOrWhiteSpace(cacheId))
                return null;

            var cacheKey = CreateCacheKeyFromCacheId(cacheId.ToLower());
            //get item in cache.
            var cache = GetItemFromCache<T>(cacheKey);
            if (cache == null)
            {
                //get item in somewhere.
                if (getItemCallback != null)
                    cache = GetItemFromCallback(getItemCallback);
                if (cache != null)
                {
                    var cacheState = new CacheState
                    {
                        CacheItem = cache,
                        CacheDirectory = _cachePath,
                        CacheFilename = cacheId
                    };
                    ThreadPool.QueueUserWorkItem(StoreCacheToFile, cacheState);
                }
                else
                {
                    //get item from file.
                    cache = GetCacheFromFile<T>(Path.Combine(_cachePath, cacheId + DefaultCacheFileExtension));
                }
                if (cache != null)
                {
                    StoreCacheToCacheManager(cacheKey, cache);
                }
            }
            return cache;
        }
      
        public T GetAsynch<T>(string cacheId, Func<T> getItemCallback, int cacheDurationMinutes, bool withSerialization) where T : class
        {
            //TODO: add function to make cache async here.
            return default(T);
        }

        #endregion

        #region Protected methods
      
        protected abstract T GetItemFromCache<T>(string cacheId);

        protected abstract void StoreCacheToCacheManager(string cacheId, object cache);
        
        protected virtual void StoreCacheToFile(object state)
        {
            try
            {
                var cacheState = state as CacheState;
                if (cacheState == null) return;
                var info = new DirectoryInfo(cacheState.CacheDirectory);
                if (!info.Exists) info.Create();
                var fullFilename = Path.Combine(cacheState.CacheDirectory, cacheState.CacheFilename + DefaultCacheFileExtension);
                _serializer.Serialize(fullFilename, cacheState.CacheItem);
            }
            catch (Exception ex)
            {
                //Logger.LogError(ex);
            }

        }
        
        #endregion

        #region Private methods

        protected T GetItemFromCallback<T>(Func<T> getItemCallback)
        {
            if (getItemCallback == null) return default(T);

            try
            {
                return getItemCallback();
            }
            catch (Exception ex)
            {
                //Logger.LogError(ex);
            }
            return default(T);
        }

        protected T GetCacheFromFile<T>(string fullFilePath) where T : class
        {
            return _serializer.Deserialize<T>(fullFilePath);
        }

        protected string CreateCacheKeyFromCacheId(string cacheId)
        {
            //combine cacheId and its hashcode and cachePath
            return string.Format("{0}_{1}", _cachePath, cacheId);
        }

        #endregion
    }
}
